import Glibc
class Timer {
    private let CLOCK_REALTIME = 0
    private var start_timespec = timespec()
    private var end_timespec = timespec()
    func start() {
        clock_gettime(Int32(CLOCK_REALTIME),&start_timespec)
    }
    
    func stop() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&end_timespec)
        let start_time = Double(start_timespec.tv_sec * 1_000_000 + start_timespec.tv_nsec / 1_000)
        let end_time = Double(end_timespec.tv_sec * 1_000_000 + end_timespec.tv_nsec / 1_000)
        let time = end_time - start_time
        return time / 1_000
    }
}
var reference: Array<Int> = [81491, 2739514];
var n: Int = 0;

class Vec3 {
    public static func set(_ out: inout Vec3, _ x: Double, _ y: Double, _ z: Double)-> Vec3 {
        out.x = x;
        out.y = y;
        out.z = z;
        return out;
    }

    public static func add(_ out: inout Vec3, _ a: Vec3, _ b: Vec3)-> Vec3 {
        out.x = a.x + b.x;
        out.y = a.y + b.y;
        out.z = a.z + b.z;
        return out;
    }

    public static func multiplyScalar(_ out: inout Vec3, _ a: Vec3, _ b: Double)-> Vec3 {
        out.x = a.x * b;
        out.y = a.y * b;
        out.z = a.z * b;
        return out;
    }

    public static func scaleAndAdd(_ out: inout Vec3, _ a: Vec3, _ b: Vec3, _ scale: Double)-> Vec3 {
        out.x = a.x + b.x * scale;
        out.y = a.y + b.y * scale;
        out.z = a.z + b.z * scale;
        return out;
    }

    public static func copy(_ out: inout Vec3, _ a: Vec3)-> Vec3 {
        out.x = a.x;
        out.y = a.y;
        out.z = a.z;
        return out;
    }


    public var x: Double;

    public var y: Double;

    public var z: Double;

    init(_ x: Double, _ y: Double, _ z: Double) {
        self.x = x;
        self.y = y;
        self.z = z;
    }
}

class Particle {
    public var particleSystem: ParticleSystemRenderCPU;
    public var position: Vec3;
    public var velocity: Vec3;
    public var animatedVelocity: Vec3;
    public var ultimateVelocity: Vec3;
    public var startSize: Vec3;
    public var size: Vec3;
    public var randomSeed: Int;
    public var remainingLifetime: Double;
    public var loopCount: Double;
    public var lastLoop: Double;
    public var trailDelay: Double;
    public var startLifetime: Double;
    public var emitAccumulator0: Double;
    public var emitAccumulator1: Double;
    public var frameIndex: Double;
    public var startRow: Double;

    init(_ particleSystem: ParticleSystemRenderCPU) {
        self.particleSystem = particleSystem;
		self.position = Vec3(0, 0, 0);
        self.velocity = Vec3(0, 0, 0);
        self.animatedVelocity = Vec3(0, 0, 0);
        self.ultimateVelocity = Vec3(0, 0, 0);
        self.startSize = Vec3(0, 0, 0);
        self.size = Vec3(0, 0, 0);
        self.randomSeed = 0; // uint
        self.remainingLifetime = 0.2;
        self.loopCount = 0;
        self.lastLoop = 0;
        self.trailDelay = 0;
        self.startLifetime = 1;
        self.emitAccumulator0 = 0.0;
        self.emitAccumulator1 = 0.0;
        self.frameIndex = 0.0;
        self.startRow = 0;
    }
}

typealias ModeType = (
    Constant: Double,
    Curve: Double,
    TwoCurves: Double,
    TwoConstants: Double
)

let Mode: ModeType = ( 
    Constant: 0,
    Curve: 1,
    TwoCurves: 2,
    TwoConstants: 3
)

func multi(_ t: Double, _ length: Double)-> Double {
    return t - Double(Int(t / length)) * length;
}

func wrapMulti(_ time: Double, _ prevTime: Double, _ nextTime: Double)-> Double {
    return prevTime + multi(time - prevTime, nextTime - prevTime);
}

func binarySearchEpsilon (_ array: [Double], _ value: Double, _ EPSILON: Double = 1e-6)-> Int {
    var low = 0;
    var high = array.count - 1;
    var middle = high >> 1;
    while(low <= high) {
        let test = array[middle];
        if (test > (value + EPSILON)) {
            high = middle - 1;
        } else if (test < (value - EPSILON)) {
            low = middle + 1;
        } else {
            return middle;
        }
        middle = (low + high) >> 1;
        n += 1;
    }
    return ~low;
}

func bezierInterpolate (_ p0: Double, _ p1: Double, _ p2: Double, _ p3: Double, _ t: Double)-> Double {
    n += 1;
    let u = 1 - t;
    let coeff0 = u * u * u;
    let coeff1 = 3 * u * u * t;
    let coeff2 = 3 * u * t * t;
    let coeff3 = t * t * t;
    return coeff0 * p0 + coeff1 * p1 + coeff2 * p2 + coeff3 * p3;
}

class RealKeyframeValue {
    public var value: Double = 0.0;
    public var rightTangent: Double = 0.0;
    public var rightTangentWeight: Double = 0.0;
    public var leftTangent: Double = 0.0;
    public var leftTangentWeight: Double = 0.0;

    init(_ a: Double, _ b: Double, _ c: Double, _ d: Double, _ e: Double) {
        self.value = a;
        self.rightTangent = b;
        self.rightTangentWeight = c;
        self.leftTangent = d;
        self.leftTangentWeight = e;
    }
}

func evalBetweenTwoKeyFrames (
    _ prevTime: Double,
	_ prevValue: RealKeyframeValue,
    _ nextTime: Double,
    _ nextValue: RealKeyframeValue,
    _ ratio: Double
)-> Double {
    let dt = nextTime - prevTime;
    let ONE_THIRD = 1.0 / 3.0;
    let prevTangentWeightEnabled = false;
    let nextTangentWeightEnabled = false;
    // let {
    //     rightTangent: prevTangent,
    //     rightTangentWeight: prevTangentWeightSpecified,
    // } = prevValue;
    // let {
    //     leftTangent: nextTangent,
    //     leftTangentWeight: nextTangentWeightSpecified,
    // } = nextValue;
    let prevTangent = prevValue.rightTangent;
    let prevTangentWeightSpecified = prevValue.rightTangentWeight;
    let nextTangent = nextValue.leftTangent;
    let nextTangentWeightSpecified = nextValue.leftTangentWeight;

    if (!prevTangentWeightEnabled && !nextTangentWeightEnabled) {
        // Optimize for the case when both x components of tangents are 1.
        // See below.
        let p1 = prevValue.value + ONE_THIRD * prevTangent * dt;
        let p2 = nextValue.value - ONE_THIRD * nextTangent * dt;
        return bezierInterpolate(prevValue.value, p1, p2, nextValue.value, ratio);
    }
    return 0;
}

enum TestError: Error{
    case ErrorOne (String)
}

func assertIsTrue (_ expr: Bool, _ message: String = "") throws {
    if (!expr) {
        throw TestError.ErrorOne("错误1")
    }
}

class RealCurve {
    public var value: Double = 0.0;
    public var rightTangent: Double = 0.0;
    public var rightTangentWeight: Double = 0.0;
    public var leftTangent: Double = 0.0;
    public var leftTangentWeight: Double = 0.0;

    private var _times: [Double] = [0.1111111, 0.555555555, 0.999999999];
    private var _values: [RealKeyframeValue] = [
        RealKeyframeValue(0.2, 0.7, 0, 0.3, 0),
        RealKeyframeValue(0.3, 0.4, 0, 0.2, 0),
        RealKeyframeValue(0.4, 0.5, 0, 0.6, 0)
    ];
    // values = [0.2,0.3,0.4]
    public func evaluate(_ time1: Double)-> Double {
        // let {
        //     _times: times,
        //     _values: values,
        // } = self;
        var time = time1;
        let times = self._times;
        let values = self._values;

        let nFrames = times.count;
        let firstTime = times[0];
        let lastTime = times[nFrames - 1];
        if (time < firstTime) {
            let preValue = values[0];
            time = wrapMulti(time, firstTime, lastTime); // 次数

        }

        let index = binarySearchEpsilon(times, time);
        if (index >= 0) {
            return values[index].value;
        }

        let iNext = ~index;
		        // assertIsTrue(iNext !== 0 && iNext !== nFrames && nFrames > 1);

        let iPre = iNext - 1;
        let preTime = times[iPre];
        let preValue = values[iPre];
        let nextTime = times[iNext];
        let nextValue = values[iNext];
        // assertIsTrue(nextTime > time && time > preTime);
        let dt = nextTime - preTime;

        let ratio = (time - preTime) / dt;
        return evalBetweenTwoKeyFrames(preTime, preValue, nextTime, nextValue, ratio);
    }
}

class CurveRange {
    public var mode: Double = Mode.Constant;

    public var spline: RealCurve = RealCurve();

    public var constant: Double = 1;

    public var multiplier: Double = 1;

    init(_ thismode: Double) {
        self.mode = thismode
    }

    public func evaluate(_ time: Double)-> Double {
        switch (self.mode) {
        case Mode.Constant:
            return self.constant;
        case Mode.Curve:
            return self.spline.evaluate(time) * self.multiplier;
        default:
            return self.constant;
        }
    }
}

class SizeModule {
    public var size: CurveRange = CurveRange(Mode.Curve);

    public var x: CurveRange = CurveRange(Mode.Curve);

    public var y: CurveRange = CurveRange(Mode.Curve);

    public var z: CurveRange = CurveRange(Mode.Curve);

    public func animate(_ particle: Particle, _ dt: Double)-> Void {
        Vec3.multiplyScalar(&particle.size, particle.startSize,
            self.size.evaluate(1 - particle.remainingLifetime / particle.startLifetime));
    }
}

class VelocityModule {
    public var x: CurveRange = CurveRange(Mode.Curve);

    public var y: CurveRange = CurveRange(Mode.Curve);

    public var z: CurveRange = CurveRange(Mode.Curve);

    public var speedModifier: CurveRange = CurveRange(Mode.Constant);

    public var space: Double = 0;

    private var needTransform: Bool;

    public var _temp_v3: Vec3 = Vec3(0,0,0);
    init() {
	   self.speedModifier.constant = 1;
        self.needTransform = false;
    }

    func animate(_ p: Particle, _ dt: Double)-> Void {
        let normalizedTime = 1 - p.remainingLifetime / p.startLifetime;
        let vel = Vec3.set(&self._temp_v3,
            self.x.evaluate(normalizedTime),
            self.y.evaluate(normalizedTime),
            self.z.evaluate(normalizedTime));
        if (self.needTransform) {
        }
        Vec3.add(&p.animatedVelocity, p.animatedVelocity, vel);
        Vec3.add(&p.ultimateVelocity, p.velocity, p.animatedVelocity);
        Vec3.multiplyScalar(&p.ultimateVelocity, p.ultimateVelocity,
            self.speedModifier.evaluate(1 - p.remainingLifetime / p.startLifetime));
    }
}

class ParticleSystemRenderCPU {
    private var _particles: [Particle];
    // private _runAnimateList: Array<any>;
    private var _sizeModule: SizeModule;
    private var _velocityModule: VelocityModule;

    init() {
        let size = 50;
        self._sizeModule = SizeModule();
        self._velocityModule = VelocityModule();
        self._particles = [];
        for _ in 0..<size {
            self._particles.append(Particle(self));
        }
        // self._runAnimateList = [new SizeModule(), new VelocityModule()]
    }

    func UpdateParticles(_ dt: Double)-> Void {
        for i in 0..<self._particles.count {
            let p = self._particles[i];
            Vec3.set(&p.animatedVelocity, 0, 0, 0);

            Vec3.copy(&p.ultimateVelocity, p.velocity);

            // self._runAnimateList.forEach((value) => {
            //     value.animate(p, dt);
            // });
            self._sizeModule.animate(p, dt);
            self._velocityModule.animate(p, dt);
            Vec3.scaleAndAdd(&p.position, p.position, p.ultimateVelocity, dt); // apply velocity.
        }
    }
}

func runCocos()->Int{
    var systems: [ParticleSystemRenderCPU] = [ParticleSystemRenderCPU](repeating: ParticleSystemRenderCPU(), count: 18);
    // print("hello")
    let timer = Timer()
    timer.start()
    for _ in 0..<600 {
        for i in 0..<18 {
            systems[i].UpdateParticles(0.5);
        }
    }
    let time = timer.stop()
    print("Cocos - RunCocos:\t"+String(time)+"\tms");
    return Int(time)
    // print("duration:", endTime - startTime)
    // print(n)
}
_ = runCocos()